package com.thd.springboottest.shironostate.configuration;

import com.thd.springboottest.shironostate.filter.*;
import com.thd.springboottest.shironostate.realm.BearerRealm;
import com.thd.springboottest.shironostate.realm.JwtRealm;
import com.thd.springboottest.shironostate.realm.PhoneMessageRealm;
import com.thd.springboottest.shironostate.realm.UserPasswordRealm;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.mgt.SubjectDAO;
import org.apache.shiro.mgt.SubjectFactory;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;
import java.util.*;

/**
 * com.thd.springboottest.shiro.configuration.ShiroConfig
 * User: devil13th
 * Date: 2020/1/23
 * Time: 16:41
 * Description: No Description
 */
@Configuration
@ConditionalOnProperty(prefix = "shiro",name = "enable",havingValue = "true")
public class ShiroConfig {
//

    @Bean
    @ConditionalOnMissingBean
    /**
     * 开启shiro 注解模式 原理是使用beanpostprocess进行代理
     * 可以在controller中的方法前加上注解
     * 如 @RequiresPermissions("userInfo:add")
     * @param securityManager
     * @return
     */
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();
        defaultAAP.setProxyTargetClass(true);
        return defaultAAP;
    }



    @Bean
    /**
     * 开启shiro 注解模式 原理是使用beanpostprocess进行代理
     * 可以在controller中的方法前加上注解
     * 如 @RequiresPermissions("userInfo:add")
     * @param securityManager
     * @return
     */
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor
                = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    @Bean
    UserPasswordRealm userPasswordRealm() {
        UserPasswordRealm realm= new UserPasswordRealm();
        realm.setCredentialsMatcher(matcher());
        return realm;
    }

    @Bean
    PhoneMessageRealm phoneMessageRealm() {
        PhoneMessageRealm realm= new PhoneMessageRealm();
        return realm;
    }

    @Bean
    BearerRealm bearerRealm() {
        BearerRealm realm= new BearerRealm();
        return realm;
    }

    @Bean
    JwtRealm jwtRealm() {
        JwtRealm realm= new JwtRealm();
//        realm.setAuthenticationCacheName("aaaaa");
        realm.setCachingEnabled(false);
        return realm;
    }

    @Bean
    /**
     * 密码匹配器
     */
    CredentialsMatcher matcher(){


        // 使用MD5加密
        //HashedCredentialsMatcher matcher = new HashedCredentialsMatcher("MD5");

        // 使用SHA256加密
        //HashedCredentialsMatcher matcher = new HashedCredentialsMatcher("sha-256");

        // 使用SHA512加密
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher("sha-512");

        // 或者使用子类


        // 加密1024次 迭代
        matcher.setHashIterations(1024);

        return matcher;
    }


    @Bean
    public SubjectDAO subjectDAO(){
        return new MyShiroSubjectDAO();
    }

    @Bean
    public SubjectFactory subjectFactory(){
         SubjectFactory subjectFactory = new MyShiroSubjectFactory();
         return subjectFactory;
    }
    @Bean
    SecurityManager securityManager() {
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        // 使用单一realm
        //manager.setRealm(myRealm());

        // 使用多realm
        List realms = new ArrayList();
        realms.add(phoneMessageRealm());
        realms.add(userPasswordRealm());
        realms.add(bearerRealm());
        realms.add(jwtRealm());
//       realms.add(new BearerRealm());
        manager.setRealms(realms);





        // 不用session 第一步
        manager.setSubjectFactory(subjectFactory());
        // 不用session 第二部
        manager.setSubjectDAO(subjectDAO());




        return manager;
    }

    @Bean
    ShiroFilterFactoryBean shiroFilterFactoryBean() {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        /**
         * 使用无状态的shiro，只需要加认证的过滤就行了，
         * 鉴权基本上都是用注解的方式在controller的方法中进行标注
         */
        // 默认的过滤器
        /*
        简写(加粗为常用)		名称			优先级(1为最高)	说明	对应Java类
        anon				匿名拦截器		1				不需要登录就能访问,一般用于静态资源,或者移动端接口	org.apache.shiro.web.filter.authc.AnonymousFilter
        authc				登录拦截器		2				需要登录认证才能访问的资源	org.apache.shiro.web.filter.authc.FormAuthenticationFilter
        authcBasic			Http拦截器		3				Http身份验证拦截器,非常用类型,不太了解	org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
        logout				登出拦截器		4				用户登出拦截器,主要属性:redirectURL退出登录后重定向的地址	org.apache.shiro.web.filter.authc.LogoutFilter
        noSessionCreation	不创建会话拦截器	5				调用 subject.getSession(false) 不会有什么问题，但是如果 subject.getSession(true) 将抛出 DisabledSessionException 异常	org.apache.shiro.web.filter.authc.NoSessionCreationFilter
        prems				权限拦截器		6				验证用户是否拥有资源权限	org.apache.shiro.web.filter.authc.PermissionsAuthorizationFilter
        port				端口拦截器		7				其主要属性: port(80) 如果用户访问该页面是非 80，将自动将请求端口改为 80 并重定向到该 80 端口	org.apache.shiro.web.filter.authc.PortFilter
        rest				rest风格拦截器	8				rest 风格拦截器，自动根据请求方法构建权限字符串构建权限字符串；非常用类型拦截器	org.apache.shiro.web.filter.authc.HttpMethodPermissionFilter
        roles				角色拦截器		9				验证用户是否拥有资源角色	org.apache.shiro.web.filter.authc.RolesAuthorizationFilter
        ssl					SSL拦截器		10				只有请求协议是https才能通过,否则你会自动跳转到https端口(443)	org.apache.shiro.web.filter.authc.SslFilter
        user				用户拦截器		11				用户拦截器，用户已经身份验证 / 记住我登录的都可；	org.apache.shiro.web.filter.authc.UserFilter
        */
        Map<String, Filter> filters = bean.getFilters();
        filters.put("anon", new MyAnonFilter());
        filters.put("authc", new MyAuthcFilter());
        filters.put("prems", new MyPermsFilter());
        filters.put("roles", new MyRoleFilter());
        filters.put("jwtFilter", new MyJwtFilter());
        filters.put("params",new MyParamsFilter());

        // 自定义一个拦截器 - 用于生成验证码
        filters.put("validatecode", new MyValidateCodeFilter());

        bean.setFilters(filters);


        bean.setSecurityManager(securityManager());
        //注意过滤器配置顺序 不能颠倒




        Map<String, String> map = new LinkedHashMap<>();

        //配置不会被拦截的地址 顺序判断，在 ShiroConfiguration 中的 shiroFilter 处配置了 /ajaxLogin=anon，意味着可以不需要认证也可以访问
        // 每个url可以匹配多个filter 例如 map.put("/jwt", "jwtFilter,prems");
        // 配置url对应的拦截器(与上面定义的拦截器名称对应)
        map.put("/jwt", "jwtFilter");
        map.put("/testAuthorizationByAnnotation","jwtFilter");
        map.put("/list", "anon");
        map.put("/logout", "anon");
        map.put("/login", "anon");
        map.put("/getPhoneLoginCode","anon");
        map.put("/doLogin", "anon");
        map.put("/simpleHash","anon");
        map.put("/pLogin","anon");
        map.put("/tokenLogin","anon");
        map.put("/showInfo","anon");
        map.put("/unauthorizedurl","anon");

        // 自定义的filter - 用于生成验证码 然后放到Session中
        map.put("/validatecode", "validatecode");

        map.put("/testRedis/*","anon");

        // 必须具有admin,alal权限
//        map.put("/perm/*","anon,authc[12345],prems[admin,alal]");
//        map.put("/perm/*","anon,prems,roles"); // 多种拦截
//        map.put("/perm/*","prems[admin]");
//        map.put("/role/*","roles[admin]");


        // 使用自定义的带有参数的filter
        map.put("/params","params[1,2,3]");

        // perms 过滤器是以url为标识 进行权限的校验
        // 通常prems后面是不带参数的(和roles不一样,可参见后面的roles[admin],因为 url就是授权资源标识)
        map.put("/testPerm","jwtFilter,prems");  // 当url匹配到testPerm时候进入到prems过滤器，判断当前用户是否有/testPerm标识的权限。一定要在前面加上jwtFilter用于先登录，否则会根据jsessionId拿缓存
        map.put("/testPermAdd","jwtFilter,prems");  // 当url匹配到testPermAdd时候进入到prems过滤器，判断当前用户是否有/testPermAdd标识的权限。一定要在前面加上jwtFilter用于先登录，否则会根据jsessionId拿缓存

        // roles 过滤器
        // roles 过滤器不会对url
        map.put("/testRole","jwtFilter,roles");  // 用roles的过滤器,如何过滤是使用过滤器的逻辑
        map.put("/testRoleAdmin","jwtFilter,roles[admin]"); // 判断当前用户是否在当前URL(/testRoleAction)上有admin角色

        map.put("/dynamicPerm","jwtFilter,prems");
        map.put("/**", "jwtFilter,authc");


        bean.setFilterChainDefinitionMap(map);


        return bean;
    }
}
